package cn.ronghuanet.car.service.impl;

import cn.ronghuanet.base.constants.BaseConstants;
import cn.ronghuanet.base.service.impl.IBaseServiceImpl;
import cn.ronghuanet.car.domain.CarType;
import cn.ronghuanet.car.mapper.CarTypeMapper;
import cn.ronghuanet.car.service.ICarTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author lijie
 * @since 2023-08-18
 */
@Service
public class CarTypeServiceImpl extends IBaseServiceImpl<CarType> implements ICarTypeService {

    @Autowired
    private CarTypeMapper carTypeMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 车辆类型树的查询方案
     * 递归: 自己调自己 必有出口   要查询n+1次数据库
     * 循环: 查询1次数据库  采纳这个方案
     * 注意:不是说递归方案不能在项目中用,而是说如果涉及到数据库操作的话,不要用递归,如果不涉及到数据库的话,递归是可以使用的
     * 缓存的经典问题
     * 缓存穿透
     * 缓存击穿
     * 缓存雪崩
     * @return
     */
    @Override
    public List<CarType> tree(Long pid) {
        ValueOperations opsForValue = redisTemplate.opsForValue();
        // 1 从redis中查询车辆类型
        Object obj = opsForValue.get(BaseConstants.CAR_TYPE_TREE_IN_REDIS_KEY);
        // 2 如果有就直接返回
        if(Objects.nonNull(obj)){
            System.out.println("缓存中有数据................");
            return (List<CarType>)obj;
        }
        System.out.println("缓存中没有数据........................");
        // 互斥锁,保证同一时间只有一个请求访问数据库, 解决缓存击穿问题
        // 1w并发请求 1...1w  只有第一个请求需要查数据库,后续请求这个时候缓存中已经有了,就没有必要查数据库
        synchronized (CarTypeServiceImpl.class){
            obj = opsForValue.get(BaseConstants.CAR_TYPE_TREE_IN_REDIS_KEY);
            if(Objects.nonNull(obj)){
                System.out.println("缓存中有数据................");
                return (List<CarType>)obj;
            }
            // 3 如果没有调getCarTypesTreeByLoop从数据库查询
            List<CarType> carTypes = getCarTypesTreeByLoop(pid);
            //List<CarType> carTypes = null;
            if(carTypes == null){
                // 如果数据库查询数据为空,就缓存一个默认值,  这是用来解决缓存穿透问题的
                opsForValue.set(BaseConstants.CAR_TYPE_TREE_IN_REDIS_KEY,new ArrayList<>());
            }else{
                // 4 将查询出的数据放入redis
                opsForValue.set(BaseConstants.CAR_TYPE_TREE_IN_REDIS_KEY,carTypes);
            }
            // 5 返回
            return carTypes;
        }
    }

    /**
     * 循环方案:只查询一次数据库
     * @param pid
     * @return
     */
    private List<CarType> getCarTypesTreeByLoop(Long pid) {
        // 1 查询出所有的车辆类型
        List<CarType> carTypes = carTypeMapper.selectAll();

        // 将list转为一个map map的key->类型的ID,map的value就是类型对象CarType
        Map<Long,CarType> map = new HashMap<>();
        for (CarType carType : carTypes) {
            map.put(carType.getId(),carType);
        }

        // 定义一个要返回的集合,这个集合是用来装一级类型的
        List<CarType> result = new ArrayList<>();

        // 2 循环所有的车辆类型
        for (CarType carType : carTypes) {
            // 3 判断当前类型是不是一级类型
            // 3.1 如果是一级类型(父Id==0),放入要返回的集合中
            if(carType.getPid() == pid){
                result.add(carType);
            }else{
                // 3.2 如果不是一级类型,找到当前类型的父类型,将自己作为父类型的儿子放进父类型的子集合中
                // 时间复杂度 n的平方次
                // 方案一:再遍历一遍所有的车辆类型,父类型一定在查询出的所有类型中
                /*for (CarType parent : carTypes) {
                    if(carType.getPid() == parent.getId()){
                        parent.getChildren().add(carType);
                        break;
                    }
                }*/
                // 方案二:查找当前类型的父类型,当前类型pid是一定在map有key存在的,这个key所对应的值就是当前类型的父类型 时间复杂度:2n
                CarType parent = map.get(carType.getPid());
                parent.getChildren().add(carType);
            }
        }
        return result;
    }

    /**
     * 递归方案实现车辆类型树的查询
     * @param pid
     * @return
     */
    private List<CarType> getCarTypesTreeByRecursion(Long pid) {
        // 1 查询出当前pid下所有的子类型
        List<CarType> parent = carTypeMapper.loadByPid(pid);

        // 2 如果没有子类型,返回null
        if(parent == null || parent.size() == 0){
            return null;
        }
        // 3 如果有子类型,循环子类型,以子类型的ID作为PID,查询出子类型的子类型
        for (CarType carType : parent) {
            // 以自己的ID作为父ID查询出自己的子类型,将查出来的子类型放到自己子类型集合中
            List<CarType> children = getCarTypesTreeByRecursion(carType.getId());
            carType.setChildren(children);
        }
        return parent;
    }

    /**
     * 对数据做增删改后,要同步更新缓存
     * 思考??  先操作数据库还是先操作缓存?
     *   如果先操作缓存再操作数据库,如果操作数据库时报错,缓存不会回滚会导致数据库和缓存数据不一致,
     *   如果先操作数据库,再操作缓存时报错了,会回滚数据库,所以要先操作数据库
     * 如何更新缓存?
     *    方案一: 先操作数据库, 查询出数据库数据设置到缓存中
     *    方案二: 直接删除缓存  采纳这种
     * @param role
     */
    @Override
    public void insert(CarType role) {
        super.insert(role);
        // 删除缓存
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS_KEY);
    }

    @Override
    public void update(CarType role) {
        super.update(role);
        // 删除缓存
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS_KEY);
    }

    @Override
    public void delete(Long id) {
        super.delete(id);
        // 删除缓存
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS_KEY);
    }
}
